package ss.pku.jsa.type.inference;

import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;

import ss.pku.jsa.type.constraint.solver.QError;


public abstract class QType {
	public static final int PRIMITIVE_QTYPE = 0;
	public static final int METHOD_QTYPE = 1;
	public static final int OBJECT_QTYPE = 2;
	public static final int REF_QTYPE = 5;
	public static final int ARRAY_QTYPE = 6;
	public QType ecr = this;
	protected Qualifier q;
	protected Location l;
	boolean original = false;
	String name;
	TypeKey type;
	public static final String INDENT = "   ";

	public static RefQType getFreshRefQType(ITypeBinding t,
			int extraDimensions, Location l, String name, boolean original,
			boolean isStatic) {
		QType qt = getFreshQType(t, extraDimensions, l, name, original, true);
		Profile.logMemStart("getFreshRefQType/new RefQType");
		RefQType rqt = new RefQType(l, original, qt, isStatic);
		Profile.logMemEnd();
		JqualPlugin.analysis.typesUsed.add(TypeKey.getTypeKey(t));
		return rqt;
	}

	private static QType getFreshQType(ITypeBinding t, Location l, String name,
			boolean original) {
		QType ret = null;

		if (t.isArray())
			throw new IllegalArgumentException(
					"Should not be calling getFreshQType with Array type: "
							+ name + "(" + t + ") at " + l);
		if (t.isNullType())
			return null;
		if (t.isPrimitive()) {
			if (t.getName().equals("void")) {
				return null;
			}
			Profile.logMemStart("getFreshQType/PrimitiveQType");
			ret = new PrimitiveQType(t, l, name, original);
			Profile.logMemEnd();
		} else if ((t.isClass()) || (t.isInterface()) || (t.isTypeVariable())) {
			Profile.logMemStart("getFreshQType/ObjectQType");
			ret = new ObjectQType(TypeKey.getTypeKey(t), l, name, original);
			Profile.logMemEnd();
		} else {
			throw new IllegalArgumentException(
					"Could not determine type for QType " + t.getName() + "("
							+ t + ") at " + l);
		}
		JqualPlugin.analysis.typesUsed.add(TypeKey.getTypeKey(t));
		ret.original = original;
		return ret;
	}

	public static QType getFreshQType(ITypeBinding typeBinding,
			int extraDimensions, Location l, String name, boolean original,
			boolean preferred) {
		if (typeBinding.isArray()) {
			typeBinding = typeBinding.getElementType();
		}
		QType ret = null;
		JqualPlugin.analysis.typesUsed.add(TypeKey.getTypeKey(typeBinding));
		if (extraDimensions > 0) {
			RefQType element = getFreshRefQType(typeBinding,
					extraDimensions - 1, l, name + "_element", original, false);
			if (element == null)
				throw new IllegalArgumentException("Element qtype is null: "
						+ typeBinding + " " + name + " " + l);
			Profile.logMemStart("getFreshQType/ArrayQType");
			ret = new ArrayQType(element, l, name, original);
			Profile.logMemEnd();
		} else {
			ret = getFreshQType(typeBinding, l, name, original);
		}
		if (ret != null) {
			ret.original = original;
		}
		return ret;
	}

	public static MethodQType getFreshQType(IMethodBinding m, Location l,
			boolean original) {
		Profile.logMemStart("getFreshQType/MethodQType");
		MethodQType qt = new MethodQType(m, l, original);
		Profile.logMemEnd();
		return qt;
	}

	public abstract QType copy(Location paramLocation, String paramString);

	public QType copy(Location l) {
		return copy(l, this.name);
	}

	public QType copy() {
		return copy(this.l, this.name);
	}

	public void setOriginal(boolean original) {
		this.original = original;
		this.q.original = original;
	}

	public abstract void setGlobal(boolean paramBoolean);

	public abstract void setPreferred(boolean paramBoolean);

	public Qualifier getQual() {
		return this.q;
	}

	public abstract boolean isPrimitive();

	public abstract int getKind();

	public boolean testInferredQuals() {
		return true;
	}

	public boolean isArray() {
		return false;
	}

	protected void setName(String name) {
		this.name = name;
		if (this.q != null)
			this.q.setName(getName());
	}

	public String getName() {
		return this.name;
	}

	public TypeKey getType() {
		return this.type;
	}

	public QType deref() {
		return this;
	}

	public QType getEcr() {
		QType ret = null;
		ret = this.ecr == this ? this : this.ecr.getEcr();
		this.ecr = ret;
		return ret;
	}

	public boolean equals(Object o) {
		if (!(o instanceof QType))
			return false;
		QType qt = (QType) o;
		return qt.getEcr() == getEcr();
	}

	public int hashCode() {
		if (this == this.ecr) {
			return super.hashCode();
		}
		return getEcr().hashCode();
	}

	public abstract void merge(QType paramQType, Location paramLocation,
			QError paramQError);

	public abstract int getSize();

	public abstract String toStringWithQuals(int paramInt);
}
